/*
 *      
 * Copyright (c) 2016-2018 Cisco Systems, Inc.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 *   Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * 
 *   Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials provided
 *   with the distribution.
 * 
 *   Neither the name of the Cisco Systems, Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
/**
 * \file nfv9.c
 *
 * \brief netflow version 9 processing implementation
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include "safe_lib.h"

#ifdef WIN32
#include "Ws2tcpip.h"
#else 
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#endif

#include <time.h>
#include "nfv9.h"
#include "pkt.h"
#include "http.h"
#include "tls.h"
#include "config.h"
#include "err.h"

/*
 * External objects, defined in joy
 */

struct nfv9_field_type nfv9_fields[] = {                                 
    { "RESERVED",                      0,   0  },                       
    { "IN_BYTES",                      1,   0  },     
    { "IN_PKTS",                       2,   0  },    
    { "FLOWS",                         3,   0  },    
    { "PROTOCOL",                      4,   1  },    
    { "TOS",                           5,   1  },    
    { "TCP_FLAGS",                     6,   1  },   
    { "L4_SRC_PORT",                   7,   2  },   
    { "IPV4_SRC_ADDR",                 8,   4  },   
    { "SRC_MASK",                      9,   1  },   
    { "INPUT_SNMP",                   10,   0  },   
    { "L4_DST_PORT",                  11,   2  },   
    { "IPV4_DST_ADDR",                12,   4  },   
    { "DST_MASK",                     13,   1  },   
    { "OUTPUT_SNMP",                  14,   0  },   
    { "IPV4_NEXT_HOP",                15,   4  },   
    { "SRC_AS",                       16,   0  },   
    { "DST_AS",                       17,   0  },   
    { "BGP_IPV4_NEXT_HOP",            18,   4  },   
    { "MUL_DST_PKTS",                 19,   0  },   
    { "MUL_DST_BYTES",                20,   0  },   
    { "LAST_SWITCHED",                21,   4  },   
    { "FIRST_SWITCHED",               22,   4  },   
    { "OUT_BYTES",                    23,   0  },   
    { "OUT_PKTS",                     24,   0  },   
    { "IPV6_SRC_ADDR",                27,   16 },   
    { "IPV6_DST_ADDR",                28,   16 },   
    { "IPV6_SRC_MASK",                29,   1  },   
    { "IPV6_DST_MASK",                30,   1  },   
    { "IPV6_FLOW_LABEL",              31,   3  },   
    { "ICMP_TYPE",                    32,   2  },   
    { "MUL_IGMP_TYPE",                33,   1  },   
    { "SAMPLING_INTERVAL",            34,   4  },   
    { "SAMPLING_ALGORITHM",           35,   1  },   
    { "FLOW_ACTIVE_TIMEOUT",          36,   2  },   
    { "FLOW_INACTIVE_TIMEOUT",        37,   2  },   
    { "ENGINE_TYPE",                  38,   1  },   
    { "ENGINE_ID",                    39,   1  },   
    { "TOTAL_BYTES_EXP",              40,   0  },   
    { "TOTAL_PKTS_EXP",               41,   0  },   
    { "TOTAL_FLOWS_EXP",              42,   0  }, 
    { "VENDOR_PROPRIETARY",           43,   0  },  /* length? */
    { "IPV4_SRC_PREFIX",              44,   4  },
    { "IPV4_DST_PREFIX",              45,   4  },
    { "MPLS_TOP_LABEL_TYPE",          46,   1  }, 
    { "MPLS_TOP_LABEL_IP_ADDR",       47,   4  },   
    { "FLOW_SAMPLER_ID",              48,   1  },   
    { "FLOW_SAMPLER_MODE",            49,   1  },   
    { "FLOW_SAMPLER_RANDOM_INTERVAL", 50,   4  },
    { "MIN_TTL",                      52,   1  },
    { "MAX_TTL",                      53,   1  },
    { "IPV4_IDENT",                   54,   2  },
    { "DST_TOS",                      55,   1  },   
    { "SRC_MAC",                      56,   6  },   
    { "DST_MAC",                      57,   6  },   
    { "SRC_VLAN",                     58,   2  },   
    { "DST_VLAN",                     59,   2  },   
    { "IP_PROTOCOL_VERSION",          60,   1  },   
    { "DIRECTION",                    61,   1  },   
    { "IPV6_NEXT_HOP",                62,  16  },   
    { "BGP_IPV6_NEXT_HOP",            63,  16  },   
    { "IPV6_OPTION_HEADERS",          64,   4  },
    { "VENDOR_PROPRIETARY",           65,   0  },
    { "VENDOR_PROPRIETARY",           66,   0  },
    { "VENDOR_PROPRIETARY",           67,   0  },
    { "VENDOR_PROPRIETARY",           68,   0  },
    { "VENDOR_PROPRIETARY",           69,   0  },
    { "MPLS_LABEL_1",                 70,   3  },   
    { "MPLS_LABEL_2",                 71,   3  },   
    { "MPLS_LABEL_3",                 72,   3  },   
    { "MPLS_LABEL_4",                 73,   3  },   
    { "MPLS_LABEL_5",                 74,   3  },   
    { "MPLS_LABEL_6",                 75,   3  },   
    { "MPLS_LABEL_7",                 76,   3  },   
    { "MPLS_LABEL_8",                 77,   3  },   
    { "MPLS_LABEL_9",                 78,   3  },   
    { "MPLS_LABEL_10",                79,   3  },   
    { "IN_DST_MAC",                   80,   6  },
    { "OUT_SRC_MAC",                  81,   6  },
    { "IF_NAME",                      82,   0  },
    { "IF_DESC",                      83,   0  },
    { "SAMPLER_NAME",                 84,   0  },
    { "IN_PERMANENT_BYTES",           85,   0  },
    { "IN_PERMANENT_PKTS",            86,   0  }, 
    { "VENDOR_PROPRIETARY",           87,   0  },
    { "FRAGMENT_OFFSET",              88,   2  },
    { "FORWARDING_STATUS",            89,   1  },
    { "MPLS_PAL_RD",                  90,   8  },
    { "MPLS_PREFIX_LEN",              91,   1  },
    { "SRC_TRAFFIC_INDEX",            92,   4  },
    { "DST_TRAFFIC_INDEX",            93,   4  },
    { "APPLICATION_DESCRIPTION",      94,   0  },
    { "APPLICATION_TAG",              95,   0  }, /* 1+n ? */
    { "APPLICATION_NAME",             96,   0  },
    { "VENDOR_PROPRIETARY",           97,   0  }, /* missing */
    { "postipDiffServCodePoint",      98,   1  },
    { "replication_factor",           99,   4  },
    { "DEPRECATED",                  100,   0  },
    { "RESERVED",                    101,   0  },
    { "layer2packetSectionOffset",   102,   0, },
    { "layer2packetSectionSize",     103,   0, },
    { "layer2packetSectionData",     104,   0, },
    { "SALT",                        105,   200, }, /* NONSTANDARD */
};

/**
 * \fn void nfv9_template_key_init (struct nfv9_template_key *k, 
                    u_long addr, u_long id, u_short template_id)
 * \param k
 * \param addr 
 * \param id
 * \param template_id
 * \return none
 */
void nfv9_template_key_init (struct nfv9_template_key *k, 
             u_long addr, u_long id, u_short template_id) {
    k->src_addr.s_addr = addr;
    k->src_id = id;
    k->template_id = template_id;
} 


static void nfv9_process_times (flow_record_t *nf_record,
         const char *time_data, struct timeval *old_val_time, 
         int max_length_array, int pkt_time_index) {
    short tmp_packet_time;
    int repeated_times;
    int j;
    for (j = 0; j < max_length_array; j += 2) {
        tmp_packet_time = htons(*(const short *)(time_data + j));

        short tmp_packet_length = htons(*(const short *)(time_data + j - max_length_array));

        // look for run length encoding
        if (tmp_packet_length < 0 && tmp_packet_length != -32768) {
            int repeated_length = tmp_packet_length * -1 - 1;
            while (repeated_length > 0) {
                if (pkt_time_index < MAX_NUM_PKT_LEN) {
                    nf_record->pkt_time[pkt_time_index] = *old_val_time;
                    pkt_time_index++;
                } else {
                    break;
                }
                repeated_length -= 1;
            }
        }

        // value represents the arrival time of the packet
        if (tmp_packet_time >= 0) {
            old_val_time->tv_sec += (time_t)(tmp_packet_time/1000);
            old_val_time->tv_usec += (unsigned long int)(tmp_packet_time - ((int)(tmp_packet_time/1000.0))*1000)*1000;

            // make sure to check for wrap around, weirdness happens when usec >= 1000000
            if (old_val_time->tv_usec >= 1000000) {
                old_val_time->tv_sec += (time_t)((int)(old_val_time->tv_usec / 1000000));
                old_val_time->tv_usec %= 1000000;
            }
      
            if (pkt_time_index < MAX_NUM_PKT_LEN) {
                nf_record->pkt_time[pkt_time_index] = *old_val_time;
                pkt_time_index++;
            } else {
                break;
            }
        } else {
            // value represents the number of packets that were observed that had an arrival time
            //   equal to the last observed arrival time
            repeated_times = tmp_packet_time * -1;
            int k;
            for (k = 0; k < repeated_times; k++) {
                if (pkt_time_index < MAX_NUM_PKT_LEN) {
                    nf_record->pkt_time[pkt_time_index] = *old_val_time;
                    pkt_time_index++;
                } else {
                    break;
                }
            }
        }
    }
}

static void nfv9_process_lengths (flow_record_t *nf_record, 
        const char *length_data, int max_length_array, int pkt_len_index) {
    int old_val = 0;
    short tmp_packet_length;
    int repeated_length;
    int j;
    for (j = 0; j < max_length_array; j += 2) {
        tmp_packet_length = htons(*(const short *)(length_data + j));
        // value represents the length of the packet
        if (tmp_packet_length >= 0) {
            if (tmp_packet_length > 0) {
                nf_record->op += 1;
            }
            old_val = tmp_packet_length;
            if (pkt_len_index < MAX_NUM_PKT_LEN) {
                nf_record->pkt_len[pkt_len_index] = tmp_packet_length;
                pkt_len_index++;
            } else {
                break;
            }
        } else {
            // value represents the number of packets that were observed that had a length
            //   equal to the last observed packet length
            // padding value, "8000", flow is done
            if (tmp_packet_length == -32768) {
                break;
            }
            repeated_length = tmp_packet_length * -1;
            nf_record->op += repeated_length;
            int k;
            for (k = 0; k < repeated_length; k++) {
                if (pkt_len_index < MAX_NUM_PKT_LEN) {
                    nf_record->pkt_len[pkt_len_index] = old_val;
                    pkt_len_index++;
                } else {
                    break;
                }
            }
        }
    }
}

/*
 * @brief Process the flow's absolute start or ending time in milliseconds.
 *
 * @param flow_data Contains the exported start/end flow time.
 * @param nf_record NFV9 flow record being encoded.
 * @param flag_end Signals whether the end or start time is being encoded.
 *        0 for start, 1 for end, anything else is invalid.
 *
 * @return 0 for success, 1 for failure
 */
static int nfv9_process_flow_time_milli(const void *flow_data,
                                        flow_record_t *nf_record,
                                        int flag_end) {
    struct timeval *time;

    switch (flag_end) {
        case 0:
            time = &nf_record->start;
            break;
        case 1:
            time = &nf_record->end;
            break;
        default:
            joy_log_err("invalid value for flag_end, must be 0 or 1");
            return 1;
    }

    if (time->tv_sec + time->tv_usec == 0) {
        time->tv_sec =
            (time_t)((uint32_t)(ntoh64(*(const uint64_t *)flow_data) / 1000));

        time->tv_usec =
            (time_t)((uint32_t)ntoh64(*(const uint64_t *)flow_data) % 1000)*1000;
    }

    return 0;
}

/**
 * \fn void nfv9_flow_key_init (flow_key_t *key, 
      const struct nfv9_template *cur_template, const void *flow_data)
 * \param key
 * \param cur_template
 * \param flow_data
 * \return none
 */
void nfv9_flow_key_init (flow_key_t *key, 
      const struct nfv9_template *cur_template, const char *flow_data) {
    int i;

    memset_s(key, sizeof(flow_key_t), 0x00, sizeof(flow_key_t));
    for (i = 0; i < cur_template->hdr.FieldCount; i++) {
        switch (htons(cur_template->fields[i].FieldType)) {
            case IPV4_SRC_ADDR:
                key->sa.v4_sa.s_addr = *(const int *)flow_data;
                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            case IPV4_DST_ADDR:
                key->da.v4_da.s_addr = *(const int *)flow_data;
                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            case L4_SRC_PORT:
                key->sp = htons(*(const short *)flow_data);
                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            case L4_DST_PORT:
                key->dp = htons(*(const short *)flow_data);
                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            case PROTOCOL:
                key->prot = *(const char *)flow_data;
                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            default:
                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
        }
    }
}

/*
 * Skip past L3/L4 header contained within the IDP flow data.
 * nf_record - NetFlow record being encoded, contains total IDP flow
 *        data originating from exporter.
 * payload - Will be assigned address of payload data that comes
 *        immediately after protocol headers.
 * size_payload - Handle for external unsigned integer
 *        that will store length of the payload data.
 */
static int nfv9_skip_idp_header(flow_record_t *nf_record,
                          const unsigned char **payload,
                          unsigned int *size_payload) {
    unsigned char proto = 0;
    const ip_hdr_t *ip = NULL;
    unsigned int ip_hdr_len;
    const unsigned char *flow_data = nf_record->idp;
    unsigned int flow_len = nf_record->idp_len;

    /* Compute ip header offset */
    ip = (const ip_hdr_t*)(flow_data);
    ip_hdr_len = ip_hdr_length(ip);
    if (ip_hdr_len < 20) {
        /*
         * FIXME Does not handle packets with all 0s.
         */
        joy_log_warn("invalid ip header of len %d", ip_hdr_len);
        return 1;
    }

    if (ntohs(ip->ip_len) < sizeof(ip_hdr_t)) {
        /* IP packet is malformed (shorter than a complete IP header) */
        joy_log_warn("ip packet malformed, ip_len: %d", ntohs(ip->ip_len));
        return 1;
    }

    proto = (unsigned char)nf_record->key.prot;

    if (proto == IPPROTO_ICMP) {
        unsigned int icmp_hdr_len = 8;

        if (icmp_hdr_len > (flow_len - ip_hdr_len)) {
             joy_log_warn("not enough space in payload for icmp hdr");
             return 1;
        }
        /* Compute icmp payload (segment) offset */
        *payload = (const unsigned char *)(flow_data + ip_hdr_len + icmp_hdr_len);

        /* Compute icmp payload (segment) size */
        *size_payload = flow_len - ip_hdr_len - icmp_hdr_len;
    } else if (proto == IPPROTO_TCP) {
        unsigned int tcp_hdr_len;
        const struct tcp_hdr *tcp = (const struct tcp_hdr *)(flow_data + ip_hdr_len);
        tcp_hdr_len = tcp_hdr_length(tcp);

        if (tcp_hdr_len < 20 || tcp_hdr_len > (flow_len - ip_hdr_len)) {
            joy_log_warn("invalid tcp hdr length"); 
            return 1;
        }

        /* Compute tcp payload (segment) offset */
        *payload = (const unsigned char *)(flow_data + ip_hdr_len + tcp_hdr_len);

        /* Compute tcp payload (segment) size */
        *size_payload = flow_len - ip_hdr_len - tcp_hdr_len;
    } else if (proto == IPPROTO_UDP) {
        unsigned int udp_hdr_len = 8;

        /* Compute udp payload (segment) offset */
        *payload = (const unsigned char *)(flow_data + ip_hdr_len + udp_hdr_len);

        /* Compute udp payload (segment) size */
        *size_payload = flow_len - ip_hdr_len - udp_hdr_len;
    } else {
        joy_log_warn("transport protocol not supported");
        return 1;
    }

    return 0;
}

/**
 * \fn void nfv9_process_flow_record (flow_record_t *nf_record, 
        const struct nfv9_template *cur_template, const void *header,
        unsigned int header_len, const void *flow_data, int record_num)
 * \param nf_record
 * \param cur_template
 * \param flow_data
 * \param record_num
 * \return none
*/
void nfv9_process_flow_record (flow_record_t *nf_record, 
			       const struct nfv9_template *cur_template, 
			       const char *flow_data, int record_num) {

    const struct pcap_pkthdr *header = NULL;   /* dummy */
    struct timeval old_val_time;
    unsigned int total_ms = 0;
    const unsigned char *payload = NULL;
    unsigned int size_payload = 0;
    flow_record_t *record = nf_record;
    int i,j = 0;
    int field_length = 0;
    int bytes_per_val = 0;

    memset_s(&old_val_time, sizeof(struct timeval), 0x0, sizeof(struct timeval));

    for (i = 0; i < cur_template->hdr.FieldCount; i++) {

        switch (htons(cur_template->fields[i].FieldType)) {
            case IN_BYTES:
                nf_record->ob += (unsigned int)ntoh64(*(const uint64_t *)flow_data);
                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            case IN_PKTS:
                if (record_num == 0) {
                    if (htons(cur_template->fields[i].FieldLength) == 4) {
                        nf_record->np += htonl(*(const int *)(flow_data));
                    } else {
                        nf_record->np += hton64(*(const uint64_t *)(flow_data));
                    }
                }
      
                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
      
            case FIRST_SWITCHED:
                if (nf_record->start.tv_sec + nf_record->start.tv_usec == 0) {
                    nf_record->start.tv_sec = (time_t)((int)(htonl(*(const unsigned int *)flow_data) / 1000));
                    nf_record->start.tv_usec = (time_t)((int)htonl(*(const unsigned int *)flow_data) % 1000)*1000;
                }

                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            case LAST_SWITCHED:
                if (nf_record->end.tv_sec + nf_record->end.tv_usec == 0) {
                    nf_record->end.tv_sec = (time_t)((int)(htonl(*(const int *)flow_data) / 1000));
                    nf_record->end.tv_usec = (time_t)((int)htonl(*(const int *)flow_data) % 1000)*1000;
                }

                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            case NFV9_FLOW_START_MILLISECONDS:
                nfv9_process_flow_time_milli(flow_data, nf_record, 0);

                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            case NFV9_FLOW_END_MILLISECONDS:
                nfv9_process_flow_time_milli(flow_data, nf_record, 1);

                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            case TLS_SRLT:
                /* if TLS structure is NULL get one */
                if (nf_record->tls == NULL) {
                    tls_init(&nf_record->tls);
                    /* if still NULL bail on this processing */
                    if (nf_record->tls == NULL) {
                        flow_data += htons(cur_template->fields[i].FieldLength);
                        break;
                    }
                }

                /* specify role as flow data if necessary */
                if (nf_record->tls->role == role_unknown) {
                    nf_record->tls->role = role_flow_data;
                }
                total_ms = 0;
                for (j = 0; j < 20; j++) {
                    if (htons(*(const short *)(flow_data+j*2)) == 0) {
                        break;
                    }

                    nf_record->tls->lengths[j] = htons(*(const unsigned short *)(flow_data+j*2));
                    nf_record->tls->times[j].tv_sec = (total_ms+htons(*(const unsigned short *)(flow_data+40+j*2))+nf_record->start.tv_sec*1000+nf_record->start.tv_usec/1000)/1000;
                    nf_record->tls->times[j].tv_usec = ((total_ms+htons(*(const unsigned short *)(flow_data+40+j*2))+nf_record->start.tv_sec*1000+nf_record->start.tv_usec/1000)%1000)*1000;
                    total_ms += htons(*(const unsigned short *)(flow_data+40+j*2));

                    nf_record->tls->msg_stats[j].content_type = *(const unsigned char *)(flow_data+80+j);
                    nf_record->tls->msg_stats[j].handshake_types[0] = *(const unsigned char *)(flow_data+100+j);
                    nf_record->tls->msg_stats[j].num_handshakes = 1;
                    nf_record->tls->op += 1;
                }
      
                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            case TLS_CS:
                /* if TLS structure is NULL get one */
                if (nf_record->tls == NULL) {
                    tls_init(&nf_record->tls);
                    /* if still NULL bail on this processing */
                    if (nf_record->tls == NULL) {
                        flow_data += htons(cur_template->fields[i].FieldLength);
                        break;
                    }
                }

                /* specify role as flow data if necessary */
                if (nf_record->tls->role == role_unknown) {
                    nf_record->tls->role = role_flow_data;
                }
                for (j = 0; j < 125; j++) {
                    if (htons(*(const short *)(flow_data+j*2)) == 65535) {
                        break;
                    }
                    nf_record->tls->ciphersuites[j] = htons(*(const unsigned short *)(flow_data+j*2));
                    nf_record->tls->num_ciphersuites += 1;
                }
      
                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            case TLS_EXT:
                /* if TLS structure is NULL get one */
                if (nf_record->tls == NULL) {
                    tls_init(&nf_record->tls);
                    /* if still NULL bail on this processing */
                    if (nf_record->tls == NULL) {
                        flow_data += htons(cur_template->fields[i].FieldLength);
                        break;
                    }
                }

                /* specify role as flow data if necessary */
                if (nf_record->tls->role == role_unknown) {
                    nf_record->tls->role = role_flow_data;
                }
                for (j = 0; j < 35; j++) {
                    if (htons(*(const short *)(flow_data+j*2)) == 0) {
                        break;
                    }
                    nf_record->tls->extensions[j].length = htons(*(const unsigned short *)(flow_data+j*2));
                    nf_record->tls->extensions[j].type = htons(*(const unsigned short *)(flow_data+70+j*2));
                    nf_record->tls->extensions[j].data = NULL;
                    nf_record->tls->num_extensions += 1;
                }
      
                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            case TLS_VERSION:
                /* if TLS structure is NULL get one */
                if (nf_record->tls == NULL) {
                    tls_init(&nf_record->tls);
                    /* if still NULL bail on this processing */
                    if (nf_record->tls == NULL) {
                        flow_data += htons(cur_template->fields[i].FieldLength);
                        break;
                    }
                }

                /* specify role as flow data if necessary */
                if (nf_record->tls->role == role_unknown) {
                    nf_record->tls->role = role_flow_data;
                }
                nf_record->tls->version = *(const char *)flow_data;
                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            case TLS_CLIENT_KEY_LENGTH:
                /* if TLS structure is NULL get one */
                if (nf_record->tls == NULL) {
                    tls_init(&nf_record->tls);
                    /* if still NULL bail on this processing */
                    if (nf_record->tls == NULL) {
                        flow_data += htons(cur_template->fields[i].FieldLength);
                        break;
                    }
                }

                /* specify role as flow data if necessary */
                if (nf_record->tls->role == role_unknown) {
                    nf_record->tls->role = role_flow_data;
                }
                nf_record->tls->client_key_length = htons(*(const short *)flow_data);
                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            case TLS_SESSION_ID:
                /* if TLS structure is NULL get one */
                if (nf_record->tls == NULL) {
                    tls_init(&nf_record->tls);
                    /* if still NULL bail on this processing */
                    if (nf_record->tls == NULL) {
                        flow_data += htons(cur_template->fields[i].FieldLength);
                        break;
                    }
                }

                /* specify role as flow data if necessary */
                if (nf_record->tls->role == role_unknown) {
                    nf_record->tls->role = role_flow_data;
                }
                nf_record->tls->sid_len = htons(*(const short *)flow_data);
                nf_record->tls->sid_len = min(nf_record->tls->sid_len,256);
                memcpy_s(nf_record->tls->sid, nf_record->tls->sid_len, flow_data+2, nf_record->tls->sid_len);
                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            case TLS_HELLO_RANDOM:
                /* if TLS structure is NULL get one */
                if (nf_record->tls == NULL) {
                    tls_init(&nf_record->tls);
                    /* if still NULL bail on this processing */
                    if (nf_record->tls == NULL) {
                        flow_data += htons(cur_template->fields[i].FieldLength);
                        break;
                    }
                }

                /* specify role as flow data if necessary */
                if (nf_record->tls->role == role_unknown) {
                    nf_record->tls->role = role_flow_data;
                }
                memcpy_s(nf_record->tls->random, 32, flow_data, 32);
                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            case IDP: 
                if (nf_record->idp != NULL) {
                    free(nf_record->idp);
                }
                nf_record->idp_len = htons(cur_template->fields[i].FieldLength);
                nf_record->idp = calloc(1, nf_record->idp_len);
                if (!nf_record->idp) {
                    return;
                }

                memcpy_s(nf_record->idp, nf_record->idp_len, flow_data, nf_record->idp_len);
                

                /* Get the start of IDP packet payload */
                payload = NULL;
                size_payload = 0;
                if (nfv9_skip_idp_header(nf_record, &payload, &size_payload)) {
                    /* Error skipping idp header */
                    flow_data += htons(cur_template->fields[i].FieldLength);
                    break;
                }

                /* Update all enabled feature modules */
                update_all_features(feature_list);
                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            case SPLT:
            {
                int max_length_array = (int)htons(cur_template->fields[i].FieldLength)/2;
                const void *length_data = flow_data;
                const void *time_data = flow_data + max_length_array;
      
                int pkt_len_index = nf_record->op;
                int pkt_time_index = nf_record->op;

                // process the lengths array in the SPLT data
                nfv9_process_lengths(nf_record, length_data, max_length_array, pkt_len_index);

                // initialize the time <- this is where we should use the nfv9 timestamp
        
                if (pkt_time_index > 0) {
                    old_val_time.tv_sec = nf_record->pkt_time[pkt_time_index-1].tv_sec;
                    old_val_time.tv_usec = nf_record->pkt_time[pkt_time_index-1].tv_usec;
                } else {
                    old_val_time.tv_sec = nf_record->start.tv_sec;
                    old_val_time.tv_usec = nf_record->start.tv_usec;
                }
      

                // process the times array in the SPLT data
                nfv9_process_times(nf_record, time_data, &old_val_time, max_length_array, pkt_time_index);

                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            }
            case BYTE_DISTRIBUTION: ;
                field_length = htons(cur_template->fields[i].FieldLength);
                bytes_per_val = field_length/256;
                for (j = 0; j < 256; j++) { 
                    // 1 byte vals
                    if (bytes_per_val == 1) {
                        nf_record->byte_count[j] = (int)*(const char *)(flow_data+j*bytes_per_val);
                    } else if (bytes_per_val == 2) {
                        // 2 byte vals
                        nf_record->byte_count[j] = htons(*(const short *)(flow_data+j*bytes_per_val));  
                    } else {
                        // 4 byte vals
                        nf_record->byte_count[j] = htonl(*(const int *)(flow_data+j*bytes_per_val));
                    }
                }

                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
            default:
                flow_data += htons(cur_template->fields[i].FieldLength);
                break;
        }
    }
}

